/**
 * FSHandler 
 *
 * @author	nara_Kryptos
 * @version	
 * 	 
 */

package fshandler;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import java.util.Hashtable;


/**
 * Class Description
 *
 * File System Handler class provides an interface similar to a file system dri
-ver. It fetches directory contents , and allows file transfer .Also it enables
registration of a client to the HNFS.The public interfaces are directly callable
from a xmlrpc client on an xmlrpc server to which this class is registered. Some
interfaces are meant to be called by other FileSystemHandlers on remote servers
using xmlrpc.The static members ensure that the properties of the instances will
span accross various objects created, since the xmlrpc server creates an instance of a class 
for every invocation of a method from the client. Static methods ensure that these
static variables are properly initialised when the class is loaded and before the 
first request is processed.
	The HNFS directories or files have the following identification
	/#n/file_or_directory_path_as_in_native_filesytem
	
	n represents the Remote Server's number as seen by this Server(determined by the properties
	file), the HNFS prefix
	This path is understood by this server only.This path may mean different things to the
	other server because the "n" is as seen by the local server
	
	0 is always the local server	

	eg in linux:
	Assume /home/narasimhan is the directory shared under HNFS in local server
	and test.txt is a file in /home/narasimhan, then the HNFS path would be /0/test.txt
	
	File transfer could have been an simple "Filling a byte buffer(aka array) and sending it".But
	the size of a byte array is limited by size of "int". To overcome this, we spilt up 
	the file into fixed size buffers and composing an array of byte buffers.
 * 
 */

public class FileSystemHandler
{
	private static String basePath;
	private static XmlRpcClient xmlrpcClients[];
	private static Hashtable ClientInfo = new Hashtable();
	private static String curDir;
	private static int no_of_clients;
	private static String URLs[];
	private static String fileName;
	private static int MAXBYTES = 1024;
	
	/**
	 * Module Description
	 * Registers a client with the  HNFS by sending out the contents of the global
	 * directory
	 * @return	Object[] Custom inodes as an array of Objects(which are String 
	 * arrays in themselves)
	 */
	public Object [] register()
	{
		Object [][] tempinodes = null;
		Object [] inodes = null;
		try
		{
			//System.out.println("here");
			tempinodes = new Object[no_of_clients][];
			int totalLength = 0;
			for(int i=0; i< no_of_clients ; i++)
			{
				tempinodes[i] = list("/"+i);
				if(tempinodes[i] == null)
				{
					continue;
				}
				else
				{
					totalLength += tempinodes[i].length;
				}
			}
			inodes = new Object[totalLength];
			int l = 0;
			for (int i=0; i< no_of_clients ; i++)
			{
				if(tempinodes[i]!=null)
				{
					for( int j=0; j < tempinodes[i].length ; j++ )
					{
						inodes[l+j] = tempinodes[i][j];
					}
					l = tempinodes[i].length ;
				}
			}
		}
		catch(Exception e)
		{
			System.out.println(e);
		}
		return inodes;
	}
	/**
	 * Module Description
	 * Sets up rpc clients who can issue xmlrpc client calls on remote FileSystemHandlers
	 * 
	 */
	public static void setupRpc()
	{
		// @ Read the properties file or some config file and use that number as array size and also the urls ,basepath.Find  them all properties file
		try
		{
		read_property_file();
		//System.out.println("here in setupRPC");
		for(int i = 1 ; i < no_of_clients ; i++)
		{
			try
			{
				XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
				config.setServerURL(new URL(URLs[i]));
				config.setEnabledForExtensions(true);  
				config.setConnectionTimeout(60 * 1000);
				config.setReplyTimeout(60 * 1000);
				xmlrpcClients[i] = new XmlRpcClient();
				xmlrpcClients[i].setConfig(config);
				ClientInfo.put(String.valueOf(i),xmlrpcClients[i]);
			}
			catch(MalformedURLException e)
			{
				System.out.println("here in setuprpc" + e +" " + i);
				xmlrpcClients[i] = null ;
			}
		}
		}
		catch(Exception e)
		{
			System.out.println(e);
		}
	}
		

	/**
	 * Module Description
	 * Assigns the real path of the file/directory in HNFS 
	 * @param	String The "canonical name" of a file/directory 
	 * @return	String The Full Path of the file/directory in HNFS
	 */
	protected String fillInfoChild(String s)  
	{
		try
		{
			s = new String(curDir + "/" + s);
			return s;
		}
		catch(Exception e)
		{
			System.out.println(e);
		}
		return null;
		
	}
	/**
	 * Module Description
	 * Assings the real path of the parent directory which is nothing but the directory whose contents were required in 		 * the first place. The curDir variable is modified and returned
	 * @param	String 
	 * @return	String The parent Path in HNFS
	 */
	protected String fillInfoParent(String s)
	{
		try
		{
			if(curDir.lastIndexOf("/") == 0)
			{
				s = new String("/") ;
			}
			else
			{
				s = new String(curDir.substring(0,curDir.lastIndexOf("/")));
			}
			//System.out.println("Parent" + s);
			return s;
		}
		catch(Exception e)
		{
			System.out.println(e);
		}
		return null;
	}	

	public FileSystemHandler()
	{
		System.out.println(" Called the constructor");
	}

	/**
	 * Module Description
	 * Fills out the properties of a file or directory of a drive/directory
	 * shared under HNFS.The file/directory is the actual file / directory
	 * present in the native file system.A null input represents the Parent 
	 * directory.
	 * @param	File The file/directory in the native filesystem
	 * @return	String [] The properties filled out in a String Array
	 */
	protected String [] fillProperties(File obj)
	{
		String [] temp = new String[3];
		if(obj == null)
		{
			temp[0]="";
			temp[1]="..";
			temp[2]="";
			return temp;
		}
		try
		{
			temp[0] = obj.getName(); //The first string element in the string array of a file will be superceded by fillChildInfo
			temp[1] = obj.getName();	
			if(obj.isFile())
			{
				/*MimeInfo mimeInfo = new MimeInfo();
				temp[2] = mimeInfo.getMimeType(obj);
				System.out.println("mime-type: " + mimeInfo.getMimeType(obj));*/
				temp[2] = "file/bytes";
			}
			else
			{		
				if(obj.isDirectory())		
					temp[2] = "file/directory";
				else
					temp[2] = "";
			}
			return temp;
		}
		catch(Exception e)
		{
			System.out.println(e);
		}
		return temp;
	}
	/**
	 * Module Description
	 * The public interface for the client to list the contents of a direc
	 * -tory in HNFS by giving its full path in HNFS. Finds out whether the 
	 * directory is present in local server or remote server by checking the 
 	 * number "n"(the HNFS prefix) and executes a local_list func call on 
	 * this server or remote server.
	 * @param	dirPath	The full path of the directory
	 * @return	Object[] The contents of the directory as custom inodes (Array of Objects aka String Arrays)
	 */
	public Object[] list(String dirPath)
	{

			try{
			if(dirPath.equals("/"))
				return register();
			//System.out.println(dirPath);
			//int regexpindex = dirPath.indexOf("/");
			String suffix = dirPath.substring(1,dirPath.length());
			String prefix;
			curDir = dirPath;
			String suffix_1;
			if(suffix.indexOf("/") == -1)
			{
				prefix = new String(suffix);
				suffix_1 = "";
			}
			else
			{	
				prefix = suffix.substring(0,suffix.indexOf("/"));
				suffix_1 = suffix.substring(suffix.indexOf("/"),suffix.length());
			}
			
			//System.out.println("Prefix:"+prefix + " Suffix:" + suffix + " Suffix_1:"+suffix_1);
			XmlRpcClient client = (XmlRpcClient)ClientInfo.get(prefix);
			String [] [] inodes;
			if(client == null)
			{
				inodes = (String [][])local_list(suffix_1);
			}
			else
			{
				//System.out.println("Here in remote execute");
				Object[] params = new Object[1];
				params[0] = new String(suffix_1);
				Object temp = (Object) client.execute("FileServer.local_list",params);			
				Object [] temp1 = (Object[])temp;
				//Object[][]temp2 = (Object[][])temp1;
				inodes=null;			
				inodes = new String[((Object [])temp1).length][];
				for ( int i= 0;i<((Object [])temp1).length;i++)
				{
					Object[]x = (Object[])temp1[i];
					inodes[i] = new String[((Object [])temp1[i]).length];
					for(int j=0;j<((Object [])temp1[i]).length;j++)
					{
					 	inodes[i][j] = x[j].toString();
					}
				}
			}
			inodes[0][0] = curDir;
			inodes[0][0] = fillInfoParent(inodes[0][0]);	
			//System.out.println("here");
			for( int i = 1; i < inodes.length ; i++ )
			{
				//System.out.println(inodes[i][0]);
				inodes[i][0] = fillInfoChild(inodes[i][0]);
				//System.out.println(inodes[i][0]);
			}
			return inodes;
			}
			catch(Exception e)
			{
				System.out.println("here in list" + e);
			}
			return null;
	}

	/**
	 * Module Description
	 * Public interface for a File System Handler to execute a list call on a Remote
	 * FileSystemHandler. This will attach the BASEPATH(the path of the base folder
	 * in the Native File System).
	 * @param	suffix The path of the directory sans the HNFS prefix
	 * @return	Object[] Array of String arrays not containing the Full Path in 
			HNFS terms. This can be filled up only by the caller which may 
			even be a remote machine 
	 */
	
	public Object [][]local_list(String suffix)
	{
		try
		{
				StringBuffer tempDirPath = new StringBuffer();
				//System.out.println("Suffix" + suffix);
				if(suffix == null)
					tempDirPath.append(basePath);
				else
					tempDirPath.append(basePath + suffix) ;
				String newDirPath = tempDirPath.toString();
				newDirPath = new String(newDirPath.replace("/",File.separator));
				//System.out.println(newDirPath); 
				File dir = new File(newDirPath);
				String [] fileNames = dir.list();
				//System.out.println(fileNames[0]);
				String [][] inodes = new String[fileNames.length + 1][];
				File obj;
				inodes[0] = fillProperties(null);
				for( int i = 0; i < fileNames.length ; i++ )
				{
					//System.out.println(fileNames[i]);
					obj = new File(newDirPath +File.separator+fileNames[i]);
					inodes[i+1] = fillProperties(obj);
					
				}
				return inodes;
		}
		catch(Exception e)
		{
	
			System.out.println(e);
		}
		return null;
	}
	/**
	 * Module Description
	 * Reads the file which contains details abt Basepath , no of clients
	 *  and Urls of other File System Handlers
	 *
	 */
	private static void read_property_file()
	{
		try
		{
           		File f = new File(fileName);	
			LineNumberReader in = new LineNumberReader(new FileReader(f));
			String str;
			str = in.readLine();
			basePath = str;
			str = in.readLine();
			no_of_clients = (new Integer(str)).intValue() + 1 ;
			URLs = new String[no_of_clients];
			// @ The no of clients is what the user gives plus 1.The first one is always the local server which
			// @ is conviniently ignored at some places because setting its value doesnt matter.
			xmlrpcClients = new XmlRpcClient[no_of_clients];
			for(int i=1;i<no_of_clients;i++)
			{
				str = in.readLine();
				URLs[i] = new String(str);
			}		
		}
 		catch(Exception e)
		{
			System.out.println("here in read_property_file"+e);
		}		
	}
	/**
	 * Module Description
	 * Fetches the contents of a file .Again this decides whether the file 
	 * is in local or remote system.
	 * @param	filePath The path of the file in HNFS	
	 * @return	Object [] Array of byte arrays. This is done to support 
			files greater than 10 MB.Current tested support is ~32 MB.
	 */
 	public Object[] get(String filePath)
	{
		try{
			String suffix = filePath.substring(1,filePath.length());
			String prefix;
			curDir = filePath;
			String suffix_1;	
			if(suffix.indexOf("/") == -1)
			{
				prefix = new String(suffix);
				suffix_1 = "";
			}
			else
			{	
				prefix = suffix.substring(0,suffix.indexOf("/"));
				suffix_1 = suffix.substring(suffix.indexOf("/"),suffix.length());
			}
			//System.out.println("Prefix:"+prefix + " Suffix:" + suffix + " Suffix_1:"+suffix_1);
			
			XmlRpcClient client = (XmlRpcClient)ClientInfo.get(prefix);
			Object [] bytes;
			if(client == null)
			{
				bytes = (Object [])local_get(suffix_1);
			}
			else
			{
				//System.out.println("Here in remote execute");
				Object[] params = new Object[1];
				params[0] = new String(suffix_1);
				Object temp = (Object) client.execute("FileServer.local_get",params);	
				bytes = (Object [] ) temp;
			}
			return bytes;
		}
		catch (Exception e)
		{
			System.out.println(e);
		}
		return null;
	}
	/**
	 * Module Description
	 * This does the actual work of fetching the file and composing pieces
	 * of byte buffers into an array
	 * @param	suffix_1 The path of the file sans HNFS prefix	
	 * @return	Object [] Array of byte arrays
	 */	
	public Object [] local_get(String suffix_1)
	{
		try
		{
			String actualPath = (basePath + suffix_1).replace("/",File.separator);			
			//System.out.println("Actual Path:"+actualPath);
			File fileobj = new File(actualPath);
			if(fileobj.canRead() == false)
			return null;
			else
			{
				long fileLength = fileobj.length();
				int no_of_pieces = (int)(((fileLength%MAXBYTES)==0)?(fileLength/MAXBYTES):(fileLength/MAXBYTES)+1);
				Object [] b_main = new Object[no_of_pieces];
				long remainingbytes=fileLength;
				int i = 0;
				int c = 0;
				FileInputStream f = new FileInputStream(fileobj);
				while(remainingbytes>0)
				{
					byte[] b = new byte[MAXBYTES];
					c = f.read(b);
					if(c == -1)
					{
						b_main[i] = (Object)b;
						break;
					}
					b_main[i] = b;
					i++;
					remainingbytes -= c;
				}
				return b_main;
			}
		}
		catch(Exception e)
		{
			System.out.println(e);
		}	
		return null;

	}
	/**
	 * Module Description
	 * Sets the fileName of the property file
	 * @param	suffix_1 The path of the file sans HNFS prefix	
	 */	
	public static void setFileName(String fname)
	{
		fileName = fname;	
	}

	
}
